package ru.spb.osll.myfit.server.execute;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;

import ru.spb.osll.myfit.client.service.PageExecutingService;
import ru.spb.osll.myfit.common.*;
import ru.spb.osll.myfit.common.filter.FilterSerializer;
import ru.spb.osll.myfit.common.filter.PageResultFilter;
import ru.spb.osll.myfit.server.Configurator;
import ru.spb.osll.myfit.server.MetadataConfigurator;
import ru.spb.osll.myfit.server.PropertyReadException;
import ru.spb.osll.myfit.server.parser.TableParser;
import ru.spb.osll.myfit.util.FileHelper;
import ru.spb.osll.myfit.util.StringUtils;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class PageExecutingServiceImpl extends RemoteServiceServlet implements
    PageExecutingService {
  private Logger log = Logger.getLogger(PageExecutingServiceImpl.class);

  private List<MetadataColumnDef> m_metadataColDef = new ArrayList<MetadataColumnDef>();

  private static final long serialVersionUID = 3889574505512661341L;

  private static final HashMap<String, PageExecutor> m_pageExecutors = new HashMap<String, PageExecutor>();

  protected Configurator m_configurator;

  protected FileHelper m_helper;

  protected PageExecutorFactory m_pageExecutorFactory;

  private Pattern m_processedPattern = Pattern.compile("processed:\\s+(\\d*)");
  private Pattern m_rightPattern = Pattern.compile("right:\\s+(\\d*)");
  private Pattern m_wrongPattern = Pattern.compile("wrong:\\s+(\\d*)");
  private Pattern m_ignoredPattern = Pattern.compile("ignored:\\s+(\\d*)");
  private Pattern m_exceptionsPattern = Pattern.compile("exceptions:\\s+(\\d*)");


  public PageExecutingServiceImpl() {
    createParams();

    MetadataConfigurator configurator = new MetadataConfigurator();
    try {
      m_metadataColDef = configurator.readProp();
    } catch (PropertyReadException e) {
      log.error("can't read metadata properties: " + e.getMessage());
    }
  }

  protected void createParams() {
    m_configurator = Configurator.getInstance();
    m_helper = new FileHelper();
    m_pageExecutorFactory = new PageExecutorFactory(m_configurator,
        new FileHelper());
  }

  @Override
  public PageExecutingResult getExecutingResult(String id) {
    PageExecutor executor;
    synchronized (m_pageExecutors) {
      executor = m_pageExecutors.get(id);
    }

    if (executor == null)
      return getResultFromFs(id);

    PageExecutingResult result = executor.getPageExecutingResult();
    if (result.getState() == ExecutingResult.ExecutingState.FINISH ||
        result.getState() == ExecutingResult.ExecutingState.FAIL) {
      synchronized (m_pageExecutors) {
        m_pageExecutors.remove(id);
      }
    }
    return result;
  }

  @Override
  public List<PageExecutingResult> getExecutingResultsBatch(Set<String> ids) {
    List<PageExecutingResult> results = new ArrayList<PageExecutingResult>();
    for (String id : ids) {
      results.add(getExecutingResult(id));
    }
    return results;
  }

  @Override
  public Set<PageExecutingResult> getResultHeadersBatch(Set<String> ids) {
    Set<PageExecutingResult> results = new LinkedHashSet<PageExecutingResult>();
    for (String id : ids) {
      results.add(getExecutingResult(id).removeAllTables());
    }
    return results;
  }

  @Override
  public PageExecutingResult executePage(final String pagePath, Map<String, String> testRunMetadata) {
    HttpServletRequest req = getThreadLocalRequest();
    if(req != null) {
      testRunMetadata.put(TestResultMetadataConstants.USER_NAME, 
          req.getRemoteUser() == null ? "" : getThreadLocalRequest().getRemoteUser());
    }
    try {
      PageExecutor pageExecutor = m_pageExecutorFactory
          .createPageExecutor(pagePath, testRunMetadata);
      pageExecutor.start();

      PageExecutingResult result = pageExecutor.getPageExecutingResult();

      if (result.getState() != ExecutingResult.ExecutingState.FINISH &&
              result.getState() != ExecutingResult.ExecutingState.FAIL) {
        synchronized (m_pageExecutors) {
          m_pageExecutors.put(pageExecutor.getTestId(), pageExecutor);
        }
      }

      return result;

    } catch (Exception e) {
      log.error("can't execute page: " + pagePath, e);
      PageExecutingResult failResult = new PageExecutingResult();
      failResult.setState(ExecutingResult.ExecutingState.FAIL);
      return failResult;
    }
  }

  @Override
  public Set<PageExecutingResult> getTestResults(String filtersString) {
    log.info("getTestResults(" + filtersString + ")");
    List<PageResultFilter> filters = new FilterSerializer().deserialize(filtersString);
    return getTestResults(filters);
  }

  public Set<PageExecutor> createPageExecutorsForSuite(List<String> pagesPaths, Map<String, String> metadata, String suiteResultId) {
    Set<PageExecutor> pageExecutors;

    HttpServletRequest req = getThreadLocalRequest();
    if (req != null) {
      metadata.put(TestResultMetadataConstants.USER_NAME,
              req.getRemoteUser() == null ? "" : getThreadLocalRequest().getRemoteUser());
    }
    try {
      pageExecutors = m_pageExecutorFactory.createPageExecutorsForSuite(pagesPaths, metadata, suiteResultId);

      synchronized (m_pageExecutors) {
        for (PageExecutor pageExecutor: pageExecutors) {
          m_pageExecutors.put(pageExecutor.getTestId(), pageExecutor);
        }
      }

      return pageExecutors;
    } catch (Exception e) {
      log.error("can't create executor for suite: " + StringUtils.substringFromPattern(FileHelper.FORMAT_PATTERN, suiteResultId, 1), e);
      return null;
    }
  }

  public Set<PageExecutingResult> getTestResults(List<PageResultFilter> filters) {
    HashMap<String, PageExecutingResult> map = new HashMap<String, PageExecutingResult>();
    Set<PageExecutingResult> results = new HashSet<PageExecutingResult>();
    getMapTests(map);
    getFsTests(map);

    Set<Map.Entry<String, PageExecutingResult>> set = map.entrySet();
    for (Map.Entry<String, PageExecutingResult> aSet : set) {
      PageExecutingResult result = aSet.getValue();
      if (!result.belongsToSuite() && filter(filters, result)) {
        results.add(result);
      }
    }

    return results;
  }

  private boolean filter(List<PageResultFilter> filters, PageExecutingResult result) {
    for (PageResultFilter filter : filters) {
      if (!filter.filter(result)) {
        return false;
      }
    }
    return true;
  }

  @Override
  public Set<PageExecutingResult> getTestResultHeaders(String filtersString) {
    Set<PageExecutingResult> pageExResults = getTestResults(filtersString);
    Set<PageExecutingResult> newResults = new HashSet<PageExecutingResult>();
    for(PageExecutingResult res : pageExResults) {
      PageExecutingResult result = new PageExecutingResult(res);
      result.removeAllTables();
      newResults.add(result);
    }
    return newResults;
  }

  protected void getMapTests(HashMap<String, PageExecutingResult> map) {
    HashMap<String, PageExecutor> temp;

    synchronized (m_pageExecutors) {
      temp = new HashMap<String, PageExecutor>(m_pageExecutors);
    }

    Set<Map.Entry<String, PageExecutor>> set = temp.entrySet();
    for (Map.Entry<String, PageExecutor> entry : set) {
      PageExecutor executor = entry.getValue();
      map.put(entry.getKey(), executor.getPageExecutingResult());
    }
  }

  protected void getFsTests(HashMap<String, PageExecutingResult> map) {
    File root = new File(m_configurator.getTestResultsDirectory());
    File[] tests = m_helper.listFiles(root);

    if (tests != null) {
      for (File test : tests) {
        String name = test.getName();
        if (m_helper.containsPage(test)) {
          try {
            PageExecutingResult pageResult = getResultFromFs(name);
            if (pageResult != null)
              map.put(name, pageResult);
          } catch (Exception e) {
          }
        }
      }
    }
  }

  protected PageExecutingResult getResultFromFs(String id) {
    PageExecutingResult pageResult;
    String html;
    String name = StringUtils.substringFromPattern(FileHelper.FORMAT_PATTERN,
        id, 1);
    String date = StringUtils.substringFromPattern(FileHelper.FORMAT_PATTERN,
        id, 2);
    Date time;
    try {
      time = StringUtils.dateFromString(date, FileHelper.DATE_FORMAT);
      html = StringUtils.streamToString(m_helper.openInputFile(m_configurator
          .getTestResultsDirectory() +
          File.separator +
          id +
          File.separator +
          name + ".html"));
      pageResult = new PageExecutingResult(id);
      pageResult.setState(ExecutingResult.ExecutingState.FINISH);
      pageResult.setName(name);
      pageResult.setStartTime(time);
      pageResult.addAllExecutedTable((new TableParser()).parse(html));
      setCounts(html, pageResult);
      
      Map<String, String> metadata = loadMetadata(m_configurator.getTestResultsDirectory() +
            File.separator +
            id +
            File.separator +
            name + ".metadata");
      pageResult.setMetadata(metadata);
   
    } catch (Exception e) {
      log.error("There is no page " + id + " saved on the disc");
      pageResult = null;
    }

    return pageResult;
  }

  protected void setCounts(String html, PageExecutingResult pageResult) {
    if (html != null && !html.equals("")) {
      int processed = 0;
      int right = 0;
      int wrong = 0;
      int ignored = 0;
      int exceptions = 0;

      if (html.contains("<title> processed:") && html.contains("</title>")) {

        int beginIndex = html.indexOf("<title>");
        int endIndex = html.indexOf("</title>");

        String str = html.substring(beginIndex, endIndex);

        Matcher matcher = m_processedPattern.matcher(str);
        matcher.find();
        String processedLine = matcher.group(1).trim();
        if (!processedLine.equals("")) {
          processed = Integer.parseInt(processedLine);
        }

        matcher = m_rightPattern.matcher(str);
        matcher.find();
        String rightLine = matcher.group(1).trim();
        if (!rightLine.equals("")) {
          right = Integer.parseInt(rightLine);
        }

        matcher = m_wrongPattern.matcher(str);
        matcher.find();
        String wrongLine = matcher.group(1).trim();
        if (!wrongLine.equals("")) {
          wrong = Integer.parseInt(wrongLine);
        }

        matcher = m_ignoredPattern.matcher(str);
        matcher.find();
        String ignoredLine = matcher.group(1).trim();
        if (!ignoredLine.equals("")) {
          ignored = Integer.parseInt(ignoredLine);
        }

        matcher = m_exceptionsPattern.matcher(str);
        matcher.find();
        String exceptionsLine = matcher.group(1).trim();
        if (!exceptionsLine.equals("")) {
          exceptions = Integer.parseInt(exceptionsLine);
        }
      }

      if (pageResult != null) {
        pageResult.setCounts(new PageProgressCounts(right, wrong, ignored,
            exceptions, processed, processed));
      }
    }
  }
  
  private Map<String, String> loadMetadata(String filename) {
    TestResultMetadataDao dao = new TestResultMetadataDao();
    InputStream stream = null;
    try {
      stream = m_helper.openInputFile(filename);
      return dao.load(stream);
     } catch(IOException e) {
    } finally {
      m_helper.close(stream);
    }
    return new TreeMap<String, String>();
  }
  
  public void saveMetadata(Map<String, String> metadata, String id){
    if(metadata == null) return;

    String name = StringUtils.substringFromPattern(FileHelper.FORMAT_PATTERN,
            id, 1);
    String metadataFilePath = Configurator.getInstance().getTestResultsDirectory() +
            File.separator +
            id +
            File.separator +
            name + ".metadata";

    TestResultMetadataDao dao = new TestResultMetadataDao();
    OutputStream stream = null;
    FileHelper helper = new FileHelper();
    try {
      stream = helper.openFile(metadataFilePath);
      dao.save(metadata, stream);
    } catch (IOException e) {
    } finally {
      helper.close(stream);
    }
  }

  @Override
  public List<MetadataColumnDef> getMetadataColDef() {
    return m_metadataColDef;
  }

  public Set<String> getTestResultIdsFromFs() {
    Set<String> ids = new TreeSet<String>();

    File root = new File(m_configurator.getTestResultsDirectory());
    File[] tests = m_helper.listFiles(root);

    if (tests != null) {
      for (File test : tests) {
        if (m_helper.containsPage(test)) {
          ids.add(test.getName());
        }
      }
    }

    return ids;
  }
}
